﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Utility3D.Extensions
{
    public static class MemberInfoExtensions
    {
        public static bool HasAttribute<TAttribute>(this MemberInfo source)
        {
            return source.GetCustomAttributes(false).Any(o => o is TAttribute);
        }

        public static bool IsPublic(this MemberInfo source)
        {
            return source.DeclaringType.GetMember(source.Name).Any();
        }

        public static Type ReturnType(this MemberInfo source)
        {
            if (source.MemberType == MemberTypes.Property) return ((PropertyInfo) source).PropertyType;
            if (source.MemberType == MemberTypes.Field) return ((FieldInfo) source).FieldType;
            throw new Exception("Can only get return type for Fields and Properties");
        }

        public static Func<object, object> GetGetter(this MemberInfo source)
        {
            if (source.MemberType == MemberTypes.Property) return o => ((PropertyInfo) source).GetValue(o, null);
            if (source.MemberType == MemberTypes.Field) return o => ((FieldInfo) source).GetValue(o);
            throw new Exception("Can only get getters for Fields and Properties");
        }

        public static Action<object, object> GetSetter(this MemberInfo source)
        {
            var nameToMatch = source.Name;
            if (source.MemberType == MemberTypes.Field)
            {
                var field = source.DeclaringType.GetField(nameToMatch, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                if (field == null) return null;
                return (objectToModify, valueToSet) => field.SetValue(objectToModify, valueToSet);
            }
            if (source.MemberType != MemberTypes.Property) throw new Exception("Can only get setters for Fields and Properties");

            var finalProperty = source.DeclaringType.GetProperty(source.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            if (!finalProperty.CanWrite)
            {
                var field = source.ReflectedType.GetField( "_" + source.Name.ToLower()[0] + source.Name.Substring(1, source.Name.Length - 1), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                if (field == null) return null;
                return (objectToModify, valueToSet) => field.SetValue(objectToModify, valueToSet);
            }
            return (objectToModify, valueToSet) => finalProperty.SetValue(objectToModify, valueToSet, null);
        }
    }
}